/**
 *  CloudEdit- Editor colaborativo peer-to-peer
 *  Author: Miguel Souto Bartolomé
 *  Web: http://code.google.com/p/cloudedit/
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.google.gwt.cloudedit.client;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.cloudedit.shared.DatosArchivo;
import com.google.gwt.cloudedit.shared.DatosListado;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.xml.client.Document;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.RichTextArea;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.xml.client.DOMException;
import com.google.gwt.xml.client.XMLParser;

/**
 * Cloudedit es un editor colaborativo de texto enriquecido. Se basa en una
 * red distribuida peer-to-peer e implementa una intefaz de usuario. Para ello
 * dispone de una arquitectura cliente-servidor basada en RCP en la que el
 * cliente maneja la lógica de la interfaz y realiza peticiones a la red 
 * distribuida a traves del servidor, que maneja la lógica de la aplicación.
 * 
 * La interfaz de usuario está realizada a través de las bibliotecas GWT, 
 * mientras que la red distribuida implementa una red Freepastry.
 * 
 * @author Miguel Souto Bartolomé
 * @version 3.0 
 */
public class Cloudedit implements EntryPoint, ClickHandler, ChangeHandler {

	/** Declaración de constantes. */
	private static final int NUEVO = 0;
	private static final int VIEJO = 1;
	private static final int ABRIRLOCAL = 2;
	private static final int ABRIRONLINE = 3;
	private static final int GUARDAR = 4;
	private static final int EDITADO = 5;
	private static final int OFF = 6;	
	
	/** INTERVALO determina cada cuántos milisegundos debe buscarse actualizaciones. */
	private static final int INTERVALO = 500;

	/** Declaracion de widgets de la interfaz. */
	public VerticalPanel mainPanel = new VerticalPanel();
	public HorizontalPanel boxPanel = new HorizontalPanel();
	public HorizontalPanel cabecera = new HorizontalPanel();
	public MenuBar menu = new MenuBar();
	public RichTextArea area = new RichTextArea();
	public RichTextToolbar toolbar = new RichTextToolbar(area);
	public Label campoArchivo = new Label("Nuevo documento de texto");
	public Label campoEdicion = new Label("");
	public ListBox editBox = new ListBox(false);
	public ListBox notifyBox = new ListBox(false);
	public ListBox nombreBox;
	public Grid nombreTabla;
	public DialogBox popupAbrir;
	public DialogBox popupAcercaDe;
	public DialogBox popupSalvar;
	public DialogBox popupInicio;
	public DialogBox popupGuardarComo;
	public MenuBar fileMenu;
	public MenuBar helpMenu;
	public Button botonSi;
	public Button botonNo;
	public Button botonCancelarSalvar;
	public Button botonCancelarAbrir;
	public Button botonAbrir;
	public Button botonCerrar;
	public Button botonGuardarComo;
	public Button botonCancelarGuardarComo;
	public TextBox areaGuardarComo;

	/** Conexión con la parte servidor del RCP. */
	public LlamadaPastryAsync nodo = (LlamadaPastryAsync) GWT
			.create(LlamadaPastry.class);
	
	/** Temporizadores. */
	private Timer temporizador;
	private Timer actualizaciones;

	/** Declaración e inicialización de variables. */
	private boolean edicionOnline = false;
	private boolean notificaciones = true;
	private boolean edicionAnterior = false;
	private int modo = NUEVO;
	private int subModo = NUEVO;
	private int numeroArchivo = 0;
	private boolean respondido = false;
	public DatosArchivo file = new DatosArchivo();
	private String nombreArchivo = "Nuevo documento de texto";
	private String archivoExiste = "";
	private String textoAntiguo = "";
	private List<DatosListado> directorio;

	/**
	 * Punto de entrada del método. Se crea la lógica de la interfaz de usuario
	 * y se cargan los widgets.
	 */
	public void onModuleLoad() {

		// Crear comando de nuevo archivo.
		Command nuevo = new Command() {
			public void execute() {
				if ((edicionOnline == false)
						&& (!textoAntiguo.equals(area.getHTML()))) {
					if (nombreArchivo.equals("Nuevo documento de texto")) {
						subModo = NUEVO;
						modo = NUEVO;
						edicionAnterior = false;
						salvar();
					} else {
						subModo = VIEJO;
						modo = NUEVO;
						edicionAnterior = false;
						salvar();
					}
				} else {
					nuevo();
				}
			}
		};

		// Crear comando de abrir archivo localmente.
		Command abrirLocal = new Command() {
			public void execute() {
				edicionAnterior = edicionOnline;
				modo = ABRIRLOCAL;
				if ((edicionOnline == false)
						&& (!textoAntiguo.equals(area.getHTML()))) {
					salvar();
				} else {
					abrir();
				}
			}
		};

		// Crear comando de abrir archivo en línea.
		Command abrirLinea = new Command() {
			public void execute() {
				edicionAnterior = edicionOnline;
				modo = ABRIRONLINE;
				if ((edicionOnline == false)
						&& (!textoAntiguo.equals(area.getHTML()))) {
					salvar();
				} else {
					abrir();
				}
			}
		};
		
		// Crear comando de guardar archivo.
		Command guarda = new Command() {
			public void execute() {
				modo = GUARDAR;
				edicionAnterior = edicionOnline;				
				if (nombreArchivo == "Nuevo documento de texto") {
					grabarComo();
				} else {
					guardar();
				}				
			}
		};

		// Crear comando de guardar copia de archivo.
		Command guardaComo = new Command() {
			public void execute() {
				modo = GUARDAR;
				edicionAnterior = edicionOnline;
				grabarComo();
			}
		};
		
		//Crear comando de mostrar ayuda.
		Command ayuda = new Command() {
			public void execute() {
				Window.open("ayuda.html", "ayuda", "scrollbars=yes,width=500,height=500");
			}
		};
		
		//Crear comando infomativo versión y autor
		Command acercaDe = new Command() {
			public void execute() {
				about();
			}
		};

		
		// Cabecera con imagen, nombre de archivo editado y notificaciones
		// de edición
		Image logo = new Image("images/Cloudedit.png");
		campoArchivo.setStyleName("titulo");
		cabecera.add(logo);
		cabecera.add(campoArchivo);
		cabecera.add(campoEdicion);
		cabecera.setCellHorizontalAlignment(campoEdicion, HasHorizontalAlignment.ALIGN_RIGHT);
		cabecera.setCellVerticalAlignment(campoEdicion, HasVerticalAlignment.ALIGN_MIDDLE);
		cabecera.setCellVerticalAlignment(campoArchivo, HasVerticalAlignment.ALIGN_MIDDLE);

		// Submenus para archivo y modo de edición.
		fileMenu = new MenuBar(true);
		fileMenu.addItem("Nuevo", nuevo);
		fileMenu.addItem("Abrir en modo local", abrirLocal);
		fileMenu.addItem("Abrir en linea", abrirLinea);
		fileMenu.addItem("Guardar archivo", guarda);
		fileMenu.addItem("Guardar como", guardaComo);
		
		//Crear menú de ayuda
		helpMenu = new MenuBar(true);
		helpMenu.addItem("Ayuda", ayuda);
		helpMenu.addItem("Acerca de", acercaDe);
		

		// Crear menú de edición
		editBox.addItem("Edición en modo local");
		editBox.addItem("Edición en linea");
		editBox.ensureDebugId("cwListBox-dropBox");
		
		// Crear menú de notificación
		notifyBox.addItem("Notificaciones activadas");
		notifyBox.addItem("Notificaciones desactivadas");
		notifyBox.ensureDebugId("cwListBox-dropBox");
		
		//Crear menú de opciones
		boxPanel.add(editBox);
		boxPanel.add(new HTML("&nbsp;"));
		boxPanel.add(notifyBox);

		// Crear barra de menús
		menu.addItem("Archivo", fileMenu);
		menu.addItem("?", helpMenu);
		menu.setAnimationEnabled(true);

		// Crear el panel principal
		area.setSize("700px", "800px");
		mainPanel.add(cabecera);
		mainPanel.add(menu);
		mainPanel.add(toolbar);
		mainPanel.add(boxPanel);
		mainPanel.add(new HTML("&nbsp;"));
		mainPanel.add(area);
		mainPanel.setWidth(new String(Window.getClientWidth() +"px"));
		mainPanel.setCellHorizontalAlignment(area, HasHorizontalAlignment.ALIGN_CENTER);

		// Asociar el panel principal con la pagina web
		RootPanel.get().add(mainPanel);

		// Crea un temporizador que comprueba si hay actualizaciones cada cierto tiempo
		temporizador = new Timer() {
			
			/* (non-Javadoc)
			 * @see com.google.gwt.user.client.Timer#run()
			 */
			@Override
			public void run() {
				if (modo == EDITADO) {
					area.setHTML(textoAntiguo);
				}
				if ((edicionOnline == true)
						&& (nombreArchivo != "Nuevo documento de texto")) {
					modo = GUARDAR;
					comprobarActualizaciones();
				}
			}
		};
		
		// Crea un temporizador que limpia la ventana de notificaciones
		actualizaciones = new Timer() {
			
			/* (non-Javadoc)
			 * @see com.google.gwt.user.client.Timer#run()
			 */
			@Override
			public void run() {
				campoEdicion.getElement().getStyle().setProperty("backgroundColor", "white");
		    	campoEdicion.setText("");
			}
		};

		// Crea un controlador de sucesos sobre el menú de edición
		editBox.addChangeHandler(this);
		
		// Crea un controlador de sucesos sobre el menú de notificaciones
		notifyBox.addChangeHandler(this);
		
		//Crea un controlador de sucesos para el momento en que se cierre la ventana
		Window.addCloseHandler(new CloseHandler<Window>() {

            @Override
            public void onClose(CloseEvent<Window> event) {
            	String modo;
            	if (edicionOnline == true) {
            		modo = "online";
            	} else {
            		modo = "offline";
            	}
            	nodo.cerrar(file, modo, new AsyncCallback<Void>() {

        			public void onFailure(Throwable caught) {
        				Window.alert("No se ha podido cerrar.");
        			}

        			public void onSuccess(Void result) {
        				//
        			}

        		});
            }
        });
		
		Window.addResizeHandler(new ResizeHandler() {

			@Override
			public void onResize(ResizeEvent event) {
				mainPanel.setWidth(new String(event.getWidth() +"px"));					
			}
			
		});
						
		start();

		temporizador.scheduleRepeating(INTERVALO);

	}

	/**
	 * Intenta guardar los cambios del archivo de datos que esta siendo
	 * editado en la red distribuida. En caso afirmativo llama al método
	 * notifica(). 
	 */
	public void guardar() {
		edicionOnline = false;
		textoAntiguo = area.getHTML();		
		file.setContent(area.getHTML());
		nodo.graba(file, new AsyncCallback<Void>() {

			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("Fallo al grabar archivo "+file.getName());
			}

			public void onSuccess(Void result) {
				notificar();

			}
		});
	}

	/**
	 * Intenta abrir un archivo de la red Pastry.
	 *
	 * @param id el identificador único del archivo
	 */
	public void abrirArchivo(String id) {
		edicionOnline = false;
		nodo.abre(id, file.getName(), new AsyncCallback<DatosArchivo>() {

			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("No se pudo abrir archivo.");
			}
			
			public void onSuccess(DatosArchivo result) {
				file = result;
				nombreArchivo = file.getName();
				campoArchivo.setText(nombreArchivo);
				subModo = VIEJO;
				if (modo == ABRIRONLINE) {
					area.setHTML(file.getContent());					
					textoAntiguo = file.getContent();					
					edicionAnterior = true;
					edicionOnline = true;
					editBox.setItemSelected(1, true);
					
				} else if (modo == ABRIRLOCAL) {
					area.setHTML(file.getContent());
					textoAntiguo = file.getContent();
					edicionAnterior = false;
					edicionOnline = false;
					editBox.setItemSelected(0, true);
			    } else if(modo == GUARDAR) {
			    	
			    	if (notificaciones == true) {
			    		nodo.comparar(textoAntiguo, file.getContent(), new AsyncCallback<String>() {

							public void onFailure(Throwable caught) {
								area.setHTML(file.getContent());
								fallo();
								Window.alert("Error al contactar con servidor.");
							}

							public void onSuccess(String result) {
								area.setHTML(result);
								textoAntiguo = file.getContent();							
								campoEdicion.getElement().getStyle().setProperty("backgroundColor", "#ADFF2F");
						    	campoEdicion.setText(file.getIp()+" está escribiendo...");
						    	actualizaciones.schedule(2000);
								modo = EDITADO;
						    	edicionOnline = true;							
							}

						});
			    	}
			    	else {
			    		area.setHTML(file.getContent());
			    		textoAntiguo = file.getContent();							
						campoEdicion.getElement().getStyle().setProperty("backgroundColor", "#ADFF2F");
				    	campoEdicion.setText(file.getIp()+" está escribiendo...");
				    	actualizaciones.schedule(2000);
						edicionOnline = true;
			    	}
			    	
			    						    	
			    }
				modo = OFF;
			}
		});

	}
	
	

	/**
	 * Intenta grabar un nuevo archivo de datos codificado en XML en la
	 * red Pastry. En caso afirmativo llama al método notificar().
	 *
	 * @param nombre el nombre del archivo
	 */
	public void guardarComo(String nombre) {
		String nombreAnterior = file.getName();
		file.setName(nombre);
		file.setContent(area.getHTML());		
		nodo.grabaComo(file, nombreAnterior, new AsyncCallback<DatosArchivo>() {

			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("Fallo al guardar archivo "+file.getName());
			}

			public void onSuccess(DatosArchivo result) {
				Window.alert("Guardado con exito.");
				file = result;
				nombreArchivo = file.getName();
				campoArchivo.setText(nombreArchivo);
				textoAntiguo = area.getHTML();
				subModo = VIEJO;
				notificar();
			}
		});

	}

	/**
	 * Intenta enviar una notificación al resto de nodos suscritos al mismo
	 * grupo (archivo) para que actualicen su información sobre el mismo.
	 */
	public void notificar() {
		nodo.notifica("<accion>actualizar</accion>", file, new AsyncCallback<Void>() {

			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("No se pudo notificar.");
			}

			public void onSuccess(Void result) {
				if ((modo == ABRIRLOCAL) || (modo == ABRIRONLINE)) {
					abrir();
				} else if (modo == NUEVO) {
					nuevo();
				} else if (modo == GUARDAR){
					edicionOnline = edicionAnterior;
					modo = OFF;
				}
			}
		});
	}

	/**
	 * Intenta comprobar si algún otro nodo ha modificado la información
	 * del archivo que se está editando. Si se ha modificado se cargará la
	 * nueva información, si no se ha modificado pero sí lo ha hecho nuestro
	 * nodo grabará y notificará esos cambios.
	 */
	public void comprobarActualizaciones() {
		nodo.comprueba(new AsyncCallback<String>() {

			@Override
			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("No se pueden comprobar actualizaciones.");

			}

			@Override
			public void onSuccess(String result) {
				String accion;
				accion = parseXML(result);
				if (accion.equals("continuar")) {
					if (!textoAntiguo.equals(area.getHTML())) {
						guardar();
					}
				} else if (accion.equals("actualizar")) {
					abrirArchivo(file.getId());
				}
			}

		});

	}

	/**
	 * Extrae de un mensaje XML la orden que determine si debemos
	 * refrescar la información del archivo o, en su caso, guardar nuestros
	 * cambios.
	 *
	 * @param mensajeXML el mensaje XML
	 * @return la orden
	 */
	public String parseXML(String mensajeXML) {
		try {
			// parsea el documento XML en un DOM
			Document messageDom = XMLParser.parse(mensajeXML);
			String mensaje = messageDom.getElementsByTagName("accion").item(0)
					.getFirstChild().getNodeValue();
			return mensaje;

		} catch (DOMException e) {
			Window.alert("No ha podido parsearse el ducumento XML");
			return "continuar";
		}
	}

	/**
	 * Intenta obtener los datos del listado de los archivos presentes
	 * en la red Pastry. En caso afirmativo los mostrará por pantalla para 
	 * permitirnos cargar un archivo.
	 */
	public void abrir() {

		edicionOnline = false;
		editBox.setItemSelected(0, true);

		nodo.listado(new AsyncCallback<ArrayList<DatosListado>>() {

			@Override
			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("Listado falla.");

			}

			public void onSuccess(ArrayList<DatosListado> result) {
				if (result.isEmpty()) {
					Window.alert("No hay archivos para editar.");
				} else {
					directorio = result;
					abrirMenu(result);
				}
			}

		});

	}

	/**
	 * Menú para la apertura de archivos.
	 *
	 * @param result el listado de archivos presentes en la red Bunshin
	 */
	private void abrirMenu(ArrayList<DatosListado> result) {

		
		popupAbrir = new DialogBox(false);
		VerticalPanel contenido = new VerticalPanel();

		Grid encabezadoTabla = new Grid(1,3);
		nombreTabla = new Grid(result.size(), 3);
		encabezadoTabla.setText(0, 0, "Nombre");
		encabezadoTabla.setText(0, 1, "Tamaño");
		encabezadoTabla.setText(0, 2, "Fecha de creación");
		encabezadoTabla.getColumnFormatter().setStyleName(0, "firstColumn");
		encabezadoTabla.getColumnFormatter().setStyleName(1, "middleColumn");
		encabezadoTabla.getColumnFormatter().setStyleName(2, "lastColumn");
		encabezadoTabla.setStyleName("encabezado");
		contenido.add(encabezadoTabla);
		Iterator<DatosListado> itr = result.iterator();
		int fila = 0;
		while (itr.hasNext()) {
			DatosListado elemento = itr.next();
			nombreTabla.setText(fila, 0, elemento.getName());
			nombreTabla.setText(fila, 2, elemento.getCreated().toString());
			nombreTabla.setText(fila, 1, Long.toString(elemento.getSize())+ " bytes");
			fila++;
		}
		nombreTabla.getColumnFormatter().setStyleName(0, "firstColumn");
		nombreTabla.getColumnFormatter().setStyleName(1, "middleColumn");
		nombreTabla.getColumnFormatter().setStyleName(2, "lastColumn");
		nombreTabla.addClickHandler(this);
		numeroArchivo = 0;
		nombreTabla.getRowFormatter().setStyleName(numeroArchivo,"selected");
		ScrollPanel scroll = new ScrollPanel(nombreTabla);
		contenido.add(scroll);

		HorizontalPanel botones = new HorizontalPanel();
		botonAbrir = new Button("Abrir");
		botonCancelarAbrir = new Button("Cancelar");
		botones.add(botonAbrir);
		botones.add(botonCancelarAbrir);
		botones.setCellHorizontalAlignment(botonAbrir,
				HasHorizontalAlignment.ALIGN_RIGHT);
		botones.setCellHorizontalAlignment(botonCancelarAbrir,
				HasHorizontalAlignment.ALIGN_LEFT);
		contenido.add(botones);

		botonAbrir.addClickHandler(this);
		botonCancelarAbrir.addClickHandler(this);

		popupAbrir.setText("Elija el archivo a abrir");
		popupAbrir.setWidget(contenido);
		popupAbrir.setWidth("610px");
		popupAbrir.setGlassEnabled(true);
		popupAbrir.setAnimationEnabled(true);

		popupAbrir.center();

	}
	
	/**
	 * Popup que muestra la información sobre versión y autor.
	 */
	private void about() {

		popupAcercaDe = new DialogBox(false);
		VerticalPanel contenido = new VerticalPanel();
		HTML mensaje = new HTML("CloudEdit<br><br>Version: 4.0<br><br>(c): Copyright Miguel Souto Bartolomé 2010,2011<br>Visite <a href=\"https://code.google.com/p/cloudedit/\" target=\"_blank\">https://code.google.com/p/cloudedit/</a><br><br>CloudEdit se distribuye bajo licencia GPL");

		botonCerrar = new Button("Cerrar");
		contenido.add(mensaje);
		contenido.add(botonCerrar);

		botonCerrar.addClickHandler(this);
		
		popupAcercaDe.setText("Acerca de");
		popupAcercaDe.setWidget(contenido);
		popupAcercaDe.setGlassEnabled(true);
		popupAcercaDe.setAnimationEnabled(true);

		popupAcercaDe.center();

	}


	/**
	 * Menú para dejarnos decidir si queremos guardar el archivo actual.
	 */
	private void salvar() {

		respondido = false;
		popupSalvar = new DialogBox(false);
		VerticalPanel contenido = new VerticalPanel();
		HTML mensaje = new HTML("¿Desea guardar el archivo actual?");

		HorizontalPanel botones = new HorizontalPanel();
		botonSi = new Button("Si");
		botonNo = new Button("No");
		botonCancelarSalvar = new Button("Cancelar");
		botones.add(botonSi);
		botones.add(botonNo);
		botones.add(botonCancelarSalvar);
		botones.setCellHorizontalAlignment(botonSi,
				HasHorizontalAlignment.ALIGN_LEFT);
		botones.setCellHorizontalAlignment(botonNo,
				HasHorizontalAlignment.ALIGN_CENTER);
		botones.setCellHorizontalAlignment(botonSi,
				HasHorizontalAlignment.ALIGN_RIGHT);

		contenido.add(mensaje);
		contenido.add(botones);

		botonSi.addClickHandler(this);
		botonNo.addClickHandler(this);
		botonCancelarSalvar.addClickHandler(this);

		popupSalvar.setText("AVISO");
		popupSalvar.setWidget(contenido);
		popupSalvar.setGlassEnabled(true);
		popupSalvar.setAnimationEnabled(true);

		popupSalvar.center();

	}

	/**
	 * Menú para guardar un archivo con un nuevo nombre.
	 */
	private void grabarComo() {

		edicionOnline = false;

		popupGuardarComo = new DialogBox(false);
		VerticalPanel contenido = new VerticalPanel();
		HTML mensaje = new HTML("Introduzca el nuevo nombre de archivo");

		areaGuardarComo = new TextBox();
		HorizontalPanel botones = new HorizontalPanel();
		botonGuardarComo = new Button("Guardar");
		botonCancelarGuardarComo = new Button("Cancelar");
		botones.add(botonGuardarComo);
		botones.add(botonCancelarGuardarComo);
		botones.setCellHorizontalAlignment(botonGuardarComo,
				HasHorizontalAlignment.ALIGN_LEFT);
		botones.setCellHorizontalAlignment(botonCancelarGuardarComo,
				HasHorizontalAlignment.ALIGN_RIGHT);

		contenido.add(mensaje);
		contenido.add(areaGuardarComo);
		contenido.add(botones);

		botonGuardarComo.addClickHandler(this);
		botonCancelarGuardarComo.addClickHandler(this);

		popupGuardarComo.setWidget(contenido);
		popupGuardarComo.setGlassEnabled(true);
		popupGuardarComo.setAnimationEnabled(true);

		popupGuardarComo.center();
		
		areaGuardarComo.setFocus(true);

	}

	/**
	 * Menú para crear un archivo nuevo.
	 */
	private void nuevo() {
		DatosArchivo file = new DatosArchivo();
		file.setName("Nuevo documento de texto");
		file.setId(null);
		nombreArchivo = "Nuevo documento de texto";
		campoArchivo.setText(nombreArchivo);
		subModo = NUEVO;
		modo = OFF;
		area.setHTML("");
		edicionAnterior = false;
		edicionOnline = false;
		editBox.setItemSelected(0, true);
	}

	/**
	 * Intenta comprobar si un nombre de archivo ya está presente en la red
	 * Bunshin. Si el archivo no está presente guardará el actual con ese 
	 * nombre.
	 *
	 * @param text el nombre de archivo
	 */
	private void existe(String text) {
		archivoExiste = text;
		nodo.comprobarNombre(text, new AsyncCallback<String>() {

			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("Error: no se pudo comprobar si el archivo existia.");
			}
			
			public void onSuccess(String result) {
				if (result.equals("EXISTE")) {
					Window
							.alert("El archivo ya existe. Introduzca otro nombre.");
				} else {
					popupGuardarComo.hide();
					guardarComo(archivoExiste);
				}
			}
		});

	}

	/**
	 * Levanta los servicios de EasyPastry e inicializa variables
	 *  correspondientes.
	 */
	public void start() {
		popupInicio = new DialogBox(false);
		popupInicio.setText("Iniciando CloudEdit");
		HTML mensaje = new HTML("Por favor, espere unos segundos");
		popupInicio.setWidget(mensaje);
		popupInicio.setGlassEnabled(true);
		popupInicio.setAnimationEnabled(true);
		popupInicio.center();
		
		nodo.inicia(new AsyncCallback<Void>() {

			public void onFailure(Throwable caught) {
				fallo();
				Window.alert("Error de inicialización de red EasyPastry.");
			}

			public void onSuccess(Void result) {
				area.setHTML("");
				textoAntiguo = area.getHTML();
				file.setName("Nuevo documento de texto");
				popupInicio.hide();

				// Mover el foco a la ventana de edicion.
				area.setFocus(true);
			}

		});
	}

	/* (non-Javadoc)
	 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
	 */
	@Override
	public void onClick(ClickEvent event) {
		Widget sender = (Widget) event.getSource();
		
		if ((sender == nombreTabla)) {
			HTMLTable.Cell celda = nombreTabla.getCellForEvent(event);
			if (celda != null) {
				nombreTabla.getRowFormatter().setStyleName(numeroArchivo,"unselected");
				numeroArchivo = celda.getRowIndex();
				nombreTabla.getRowFormatter().setStyleName(numeroArchivo,"selected");				
			}
		}
		
		if (sender == botonCerrar) {
			popupAcercaDe.hide();
		}

		if ((sender == botonAbrir) || (sender == botonCancelarAbrir)) {
			popupAbrir.hide();
		}
		if ((sender == botonSi) || (sender == botonNo)
				|| (sender == botonCancelarSalvar)) {
			popupSalvar.hide();
		}
		if (sender == botonCancelarGuardarComo) {
			popupGuardarComo.hide();
			edicionOnline = edicionAnterior;
		}
		if ((sender == botonGuardarComo)
				&& (!areaGuardarComo.getText().equals(""))) {
			existe(areaGuardarComo.getText());
		}
		if (sender == botonSi) {
			respondido = true;
			if (subModo == NUEVO) {
				grabarComo();
			} else {
				guardar();
			}

		} else if (sender == botonNo) {
			respondido = true;
		} else if (sender == botonCancelarSalvar) {
			respondido = false;
		} else if (sender == botonAbrir) {
			abrirArchivo(directorio.get(numeroArchivo).getId());
		} else if (sender == botonCancelarAbrir) {
			edicionOnline = edicionAnterior;
			if (edicionOnline == true) {
				editBox.setItemSelected(1, true);
			}
			
		}

		if (respondido == true) {
			if (modo == NUEVO) {
				respondido = false;
				nuevo();

			} else if ((modo == ABRIRLOCAL) || (modo == ABRIRONLINE)) {
				if (sender == botonNo) {
					abrir();
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt.event.dom.client.ChangeEvent)
	 */
	@Override
	public void onChange(ChangeEvent event) {
		Widget sender = (Widget) event.getSource();

		if (sender == editBox) {
			edicionOnline = !edicionOnline;
			edicionAnterior = edicionOnline;
		} else if (sender == nombreBox) {
			numeroArchivo = nombreBox.getSelectedIndex();
		} else if (sender == notifyBox) {
			notificaciones = !notificaciones;
		}
	}
	
	/**
	 * Estabiliza el estado del interface despues de un error de conexión
	 *  con el servidor.
	 */
	private void fallo() {
		editBox.setItemSelected(0, true);
		edicionAnterior = false;
		edicionOnline = false;
		modo = OFF;		
	}
	
	
}